#include "stm32l4xx_hal.h"
#include "LOW_POWER.h"

LOW_POWER_Entry_Cfg LP_Entry_Cfg={0};

/*!
 * @brief       	重置GPIO（都会进行），或再将除外部高低速晶振复用、SWCLK、SWDIO复用的所有GPIO配置为模拟输入（false）
 *								注意：用于串口唤醒等的引脚，不可配置为模拟输入，也不可关闭
 *								在进行GPIO初始化前，先将GPIO_DeInit，但是不做也不影响，不过还是建议跑一下
 *								以优先级顺序来看：
 *								如果这一组GPIO都没用到过 那么直接不开启时钟就最省电
 *								如果这一组GPIO有引脚用过了 时钟不能关 那么就将用过的引脚配置为模拟输入
 *								切记！！！：
 *								不要将没用过的引脚配置为模拟输入 耗电量其实会稍微增加一点！
 *								不要将没用过的GPIO时钟打开以后再配置为模拟输入 耗电量会增加很多 就算配置后再关时钟也没用！
 *								尽量不要勾选CubeMX中的配置闲置引脚为模拟输入的选项 没用到的时钟还开启了会增加很多耗电
 *								低功耗模式配置：
 *								在进入STOP模式时 GPIO会保留原本的状态 所以把开启后不需要再保留的GPIO配置为模拟输入确实省电 时钟的话不用的肯定关 其他的反正都会关（除了保留的时钟）
 *								在进入SLEEP模式时 时钟并不会关闭 所以时钟应手动关闭 且将开启后的GPIO配置为模拟输入
 *								待机模式和关机模式就更不用在意GPIO口耗电了
 *								https://blog.csdn.net/weixin_53403301/article/details/129055530
 *
 * @param 	[in]	EnableNotDisable: 使所有GPIO变成模拟输入或不进行模拟配置
 *
 * @return				None
 */
__weak void GPIO_Reset_Init(bool EnableNotDisable)
{
//	HAL_GPIO_DeInit(GPIOA,GPIO_PIN_2|GPIO_PIN_3);		//用于串口唤醒的引脚 不可变动
	/*
	HAL_GPIO_DeInit(GPIOA,GPIO_PIN_0|GPIO_PIN_1
												|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7
												|GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11
												|GPIO_PIN_12|GPIO_PIN_15);
	
	HAL_GPIO_DeInit(GPIOB,GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_10
												|GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14
												|GPIO_PIN_15|GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5
												|GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9);
	
	HAL_GPIO_DeInit(GPIOC,GPIO_PIN_13|GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2
												|GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6
												|GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10
												|GPIO_PIN_11|GPIO_PIN_12);
	
	HAL_GPIO_DeInit(GPIOD,GPIO_PIN_2);
	
	HAL_GPIO_DeInit(GPIOH,GPIO_PIN_3);
	*/
	if(EnableNotDisable)
	{
		/*
		GPIO_InitTypeDef GPIO_InitStruct = {0};
		*/

		/* GPIO Ports Clock Enable */
		/*
		__HAL_RCC_GPIOC_CLK_ENABLE();
		__HAL_RCC_GPIOH_CLK_ENABLE();
		__HAL_RCC_GPIOA_CLK_ENABLE();
		__HAL_RCC_GPIOB_CLK_ENABLE();
		__HAL_RCC_GPIOD_CLK_ENABLE();
		*/

		/*Configure GPIO pins : PC13 PC0 PC1 PC2
														 PC3 PC4 PC5 PC6
														 PC7 PC8 PC9 PC10
														 PC11 PC12 */
		/*
		GPIO_InitStruct.Pin = GPIO_PIN_13|GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2
														|GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6
														|GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10
														|GPIO_PIN_11|GPIO_PIN_12;
		GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
		GPIO_InitStruct.Pull = GPIO_NOPULL;
		HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
		*/

		/*Configure GPIO pins : PA0 PA1 PA2 PA3
														 PA4 PA5 PA6 PA7
														 PA8 PA9 PA10 PA11
														 PA12 PA15 */
		/*
		GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1
														|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7
														|GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_11
														|GPIO_PIN_12|GPIO_PIN_15;
		GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
		GPIO_InitStruct.Pull = GPIO_NOPULL;
		HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
		*/
		
//		//用于串口唤醒的 不可变动
//		GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_3;
//		GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
//		GPIO_InitStruct.Pull = GPIO_NOPULL;
//		HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
		
		/*Configure GPIO pins : PB0 PB1 PB2 PB10
														 PB11 PB12 PB13 PB14
														 PB15 PB3 PB4 PB5
														 PB6 PB7 PB8 PB9 */
		/*
		GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_10
														|GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14
														|GPIO_PIN_15|GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5
														|GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9;
		GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
		GPIO_InitStruct.Pull = GPIO_NOPULL;
		HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
		*/

		/*Configure GPIO pin : PD2 */
		/*
		GPIO_InitStruct.Pin = GPIO_PIN_2;
		GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
		GPIO_InitStruct.Pull = GPIO_NOPULL;
		HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
		*/

		/*Configure GPIO pin : PH3 */
		/*
		GPIO_InitStruct.Pin = GPIO_PIN_3;
		GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
		GPIO_InitStruct.Pull = GPIO_NOPULL;
		HAL_GPIO_Init(GPIOH, &GPIO_InitStruct);
		*/
	}
}

/*!
 * @brief       	所有外设初始化配置，根据使用需求来写
 *
 * @param 	[in]	EnableNotDisable: 使能或者关闭
 *								true: 进行初始化外设（不包含时钟初始化）
 *								false: 或者关闭所有外设，所有GPIO配置为无上拉下拉且模拟输入，仅保留系统时钟和系统所需的GPIO口复用
 *								该函数在进入低功耗前调用（false）
 *								建议在进入该函数前（false）先配置用于唤醒的外设 如指定UART或RTC作为唤醒使用 然后再调用该函数 且不能关闭有唤醒功能的外设
 *								若用于唤醒后的初始化，则建议先初始化时钟，再执行该函数的初始化（true）
 *								在休眠期间使用的外设，不要关闭，也不要关闭GPIO等；相反，外设和GPIO等建议同时关闭（避免出现bug，并且也省电）
 *								未关闭，但唤醒时重复初始化外设并不受影响
 *								若未关闭的外设在运行中改变了初始化值，则建议不在唤醒时运行该初始化（前提是外设的GPIO等也没有作改动）
 *								若需要在初始化后更改初始化值，则建议要么不进行初始化且不关闭（也包括GPIO等），或重新设置新值
 *
 * @return				None
 */
__weak void PWR_Device_Init(bool EnableNotDisable)
{
	if(EnableNotDisable)
	{
		//这里是系统最初的初始化值
		
		GPIO_Reset_Init(false);  //重置GPIO		
		/*
		MX_GPIO_Init();
		MX_USART2_UART_Init();
		MX_UART4_Init();
		MX_ADC1_Init();
		MX_ADC2_Init();
		MX_TIM6_Init();
		MX_RTC_Init();
		MX_ADC3_Init();
		*/
		
		//这里放初始化后还要更改的配置，若要重新初始化，建议先运行外设DeInit
//		HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8,GPIO_PIN_SET);
//		HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4,GPIO_PIN_SET);
	}
	else
	{
		/*
//		HAL_ADC_DeInit(&hadc1);
//		HAL_ADC_DeInit(&hadc2);
//		HAL_ADC_DeInit(&hadc3);
//		HAL_UART_DeInit(&huart2);		//唤醒用的串口 最好不要关闭：若不用于唤醒 则可以关闭 GPIO等同步关闭；若用于唤醒 则不能关闭 GPIO等也不能关闭
//		HAL_UART_DeInit(&huart4);
//		HAL_TIM_Base_DeInit(&htim6);
//		HAL_RTC_DeInit(&hrtc);		//唤醒用的RTC 最好不要关闭	
	  */
		GPIO_Reset_Init(true);  //GPIO配置为复用
		
	}
}

/*!
 * @brief       	配置串口在停止模式下的唤醒   	
 *
 * @param 	[in]	UART_Cfg: UART配置
 *
 * @return				None
 */
__weak uint8_t Ctrl_UART_StopMode_WakeUp(LOW_POWER_UART_Cfg *UART_Cfg,bool EnableNotDisable)
{	
	if (!UART_Cfg->uart_handle)
	{
		return 0;
	}
	
	if(EnableNotDisable)
	{
		__HAL_RCC_WAKEUPSTOP_CLK_CONFIG(RCC_STOP_WAKEUPCLOCK_HSI);	//保留唤醒用的HSI线 串口初始化时钟也必须要配置为HSI
		HAL_UARTEx_StopModeWakeUpSourceConfig(UART_Cfg->uart_handle,UART_Cfg->UART_WakeUpStruct);
	
		__HAL_UART_ENABLE_IT(UART_Cfg->uart_handle,UART_IT_WUF);	//开启唤醒中断
        HAL_UARTEx_EnableClockStopMode(UART_Cfg->uart_handle);
		HAL_UARTEx_EnableStopMode(UART_Cfg->uart_handle);		//开启模式
	}
	else
	{
		__HAL_UART_DISABLE_IT(UART_Cfg->uart_handle,UART_IT_WUF);	//关闭唤醒中断
        HAL_UARTEx_DisableClockStopMode(UART_Cfg->uart_handle);
		HAL_UARTEx_DisableStopMode(UART_Cfg->uart_handle);		//关闭模式
	}
	
	return 1;
}

/*!
 * @brief       	配置停止模式下的外设唤醒函数 true为开启 false为关闭 （不包含RTC唤醒）
 *
 * @return				None
 */
__weak void Ctrl_Stop_Mode_WakeUp_Device(bool EnableNotDisable)
{
	uint8_t i=0;
	
	if(EnableNotDisable)
	{
		
		for(i=0;i<sizeof(LP_Entry_Cfg.Device_Cfg.UART_Cfg)/sizeof(LP_Entry_Cfg.Device_Cfg.UART_Cfg[0]);i++)
		{
			if(!Ctrl_UART_StopMode_WakeUp(&LP_Entry_Cfg.Device_Cfg.UART_Cfg[i],LP_Entry_Cfg.Device_Cfg.UART_Cfg[i].EnableNotDisable))
			{
				break;
			}
		}		
		
	}
	else
	{
		
		for(i=0;i<sizeof(LP_Entry_Cfg.Device_Cfg.UART_Cfg)/sizeof(LP_Entry_Cfg.Device_Cfg.UART_Cfg[0]);i++)
		{
			if(!Ctrl_UART_StopMode_WakeUp(&LP_Entry_Cfg.Device_Cfg.UART_Cfg[i],false))
			{
				break;	
			}				
		}
		
	}
}

/*!
 * @brief       	配置RTC在低功耗模式下的唤醒   	
 *
 * @param 	[in]	RTC_Cfg: RTC配置
 *
 * @return				None
 */
__weak void Ctrl_RTC_WakeUp(LOW_POWER_RTC_Cfg *RTC_Cfg,bool EnableNotDisable)
{	
	if(EnableNotDisable)
	{
		HAL_RTCEx_SetWakeUpTimer_IT(RTC_Cfg->rtc_handle,RTC_Cfg->counter,RTC_Cfg->clock);
	}
	else
	{
		__HAL_RTC_WAKEUPTIMER_EXTI_DISABLE_IT();	
	}
}

/*
进入低功耗的函数中 每个模式都有一个10ms的消抖 其实这个可以省略
但我们用的是HAL库 HAL库初始化时钟以后 会打开SysTick中断 如果这个开着 就一直无法进入低功耗（进入就被中断唤醒）
所以我这里加了个我自己写的延时函数 其中就有关闭SysTick的语句
如果你不用这个消抖 没啥BUG也行 但我还是建议加上去 毕竟也不差那10ms
*/

/*!
 * @brief       	进入低功耗模式   	
 *
 * @return				None
 */
__weak void Enter_Low_PWR(void)
{
	__HAL_RCC_PWR_CLK_ENABLE();
	switch(LP_Entry_Cfg.mode_flag)
	{
		case 0:
		{
			printf("[INFO] 不进入低功耗模式\n");
			break;
		}
		case 1:
		{
			printf("[INFO] 进入睡眠模式\n");
			delay_ms(10);  //消抖
			PWR_Device_Init(false);
			__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);	
			Ctrl_RTC_WakeUp(&LP_Entry_Cfg.RTC_Cfg,LP_Entry_Cfg.RTC_Cfg.EnableNotDisable);
			HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON,LP_Entry_Cfg.SLEEPEntry_Cfg.SLEEPEntry);
			__HAL_RCC_PWR_CLK_ENABLE();			
			Ctrl_RTC_WakeUp(NULL,false);	
			PWR_Device_Init(true);
			break;
		}
		case 2:
		{
			printf("[INFO] 进入停止模式\n");
			delay_ms(10);  //消抖
			PWR_Device_Init(false);			
			__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);	
			Ctrl_Stop_Mode_WakeUp_Device(true);			
			Ctrl_RTC_WakeUp(&LP_Entry_Cfg.RTC_Cfg,LP_Entry_Cfg.RTC_Cfg.EnableNotDisable);
			HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON,LP_Entry_Cfg.STOPEntry_Cfg.STOPEntry);
			__HAL_RCC_PWR_CLK_ENABLE();			
			LP_Entry_Cfg.SystemClock_Config_Fxn();
			Ctrl_Stop_Mode_WakeUp_Device(false);
			Ctrl_RTC_WakeUp(NULL,false);	
			PWR_Device_Init(true);
			break;
		}
		case 3:
		{
			printf("[INFO] 三秒后进入待机模式\n");
			delay_ms(3000);
			printf("[INFO] 进入待机模式\n");
			HAL_PWR_EnableWakeUpPin(LP_Entry_Cfg.WakeUpPin_Cfg.WakeUpPinPolarity);
			delay_ms(10);  //消抖
			__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
			Ctrl_RTC_WakeUp(&LP_Entry_Cfg.RTC_Cfg,LP_Entry_Cfg.RTC_Cfg.EnableNotDisable);
			HAL_PWR_EnterSTANDBYMode();
			break;
		}
		case 4:
		{
			printf("[INFO] 三秒后进入关机模式\n");
			delay_ms(3000);
			printf("[INFO] 进入关机模式\n");
			HAL_PWR_EnableWakeUpPin(LP_Entry_Cfg.WakeUpPin_Cfg.WakeUpPinPolarity);
			delay_ms(10);  //消抖
			__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
			Ctrl_RTC_WakeUp(&LP_Entry_Cfg.RTC_Cfg,LP_Entry_Cfg.RTC_Cfg.EnableNotDisable);
			HAL_PWREx_EnterSHUTDOWNMode();
			break;
		}
		default:
		{
			printf("[INFO] 不进入低功耗模式\n");
			break;
		}
	}
}

void Init_Enter_Low_PWR(SystemClock_Config_Callback SystemClock_Config_Fxn)
{	
	uint8_t i=0;
	memset(&LP_Entry_Cfg,0,sizeof(LP_Entry_Cfg));
	LP_Entry_Cfg.SystemClock_Config_Fxn=SystemClock_Config_Fxn;
	LP_Entry_Cfg.SLEEPEntry_Cfg.SLEEPEntry=PWR_SLEEPENTRY_WFI;
	LP_Entry_Cfg.STOPEntry_Cfg.STOPEntry=PWR_STOPENTRY_WFI;
	
	LP_Entry_Cfg.RTC_Cfg.counter=RTC_WAKEUPCLOCK_RTCCLK_DIV16;	
	
	for(i=0;i<sizeof(LP_Entry_Cfg.Device_Cfg.UART_Cfg)/sizeof(LP_Entry_Cfg.Device_Cfg.UART_Cfg[0]);i++)
	{
		LP_Entry_Cfg.Device_Cfg.UART_Cfg[i].UART_WakeUpStruct.WakeUpEvent=UART_WAKEUP_ON_READDATA_NONEMPTY;
	}	
}

